import { injectable, InjectableObject } from "../inject";

/**
 * TypeScript接口(interface)：
 * 1、类类型
 *      interface ISay {
 *          word: String;
 *          say(w: String);
 *      }
 *      class SayClass implements ISay {
 *          word: String;
 *          say(w: String) {
 *              this.world = w;
 *          }
 *      }
 * 2、函数类型
 *      interface Func {
 *          (src: string, subStr: string): boolean;
 *      }
 * 
 *      let f: Func = function(src: string, subStr: string) {
 *          return true;
 *      }
 * 
 * 3、可索引类型
 *      interface StringArray {
 *          [index: number]: string;
 *      }
 * 
 *      let myArr: StringArray;
 *      myArr = ['Hello', 'World'];
 *      let str = myArr[0];
 * 
 * 4、对象接口
 *      interface Person {
 *          name: string;
 *          age: number;
 *      }
 * 
 *      const one: Person = {
 *          name = 'Jim', age = 21
 *      }
 * 
 * 
 */

interface test {
    constructor();
}

interface Handle {
    (next: (data?: any) => boolean, retry: (timeout?: number) => boolean | 'ASYNC', end: (data?: any) => boolean): void
}

interface Finish {
    (success?: boolean, results?: any[]): void
}

class TaskItem {
    private handle: Handle = null;
    constructor(handle: Handle) {
        this.handle = handle;
    }

    public excute(next: (data?: any) => boolean, retry: (timeout?: number) => boolean | 'ASYNC', end: (data?: any) => boolean) {
        this.handle(next, retry, end);
    }
}

class Sync {
    private running = false;
    private index: number = -1;
    private list: TaskItem[] = [];
    private finish: Finish = null;

    public results: any[] = [];

    public size(): number {
        return this.list.length;
    }

    public add(handle: Handle): Sync {
        this.list.push(new TaskItem(handle));
        this.results.push(undefined);
        return this;
    }

    public start(finish?: Finish): Sync {
        if (this.running)
            return this;

        this.running = true;
        this.index = -1;
        this.finish = finish;
        this.next();

        return this;
    }

    public stop(): boolean {
        return false;
    }

    public isStop() {
        return !this.running;
    }

    private end(data?: any): boolean {
        if (!this.running) {
            return false;
        }

        if (typeof data != 'undefined') {
            this.results[this.index] = data;
        }

        this.running = false;

        this.finish && this.finish(true, this.results);

        return true;
    }

    private retry(): boolean {
        if (!this.running) {
            return false;
        }

        const taskItem = this.list[this.index];

        taskItem && taskItem.excute(
            (data?: any) => this.next(data),
            (timeout = 0) => timeout > 0 ? (setTimeout(() => this.retry(), timeout * 1000), 'ASYNC') : this.retry(),
            (data?: any) => this.end(data)
        );

        return true;
    }

    private next(data?: any): boolean {
        if (!this.running) {
            return false;
        }

        if (typeof data != 'undefined') {
            this.results[this.index] = data;
        }

        if (++this.index < this.list.length) {
            this.retry();
        } else {
            this.end()
        }

        return true;
    }
}


class ASync {

    private running = false;
    private count: number = 0;
    private list: TaskItem[] = [];
    private finish: Finish = null;

    public results: any[] = [];

    public add(handle: Handle): ASync {
        this.list.push(new TaskItem(handle));
        this.results.push(undefined);

        if (this.running) {
            this.retry(this.list.length - 1);
        }

        return this;
    }

    public start(finish?: Finish): ASync {
        if (this.running) {
            return this;
        }

        this.running = true;
        this.count = 0;
        this.finish = finish;

        if (this.list.length) {
            for (let i = 0; i < this.list.length; i++) {
                this.retry(i);
            }
        } else {
            this.end && this.end();
        }

        return this;
    }

    public stop(): boolean {
        if (!this.running) {
            return false;
        }

        this.running = false;
        this.finish && this.finish(false, this.results);
        return true;
    }

    public isStop() {
        return !this.running;
    }

    private end(index?: number, data?: any): boolean {
        if (!this.running) {
            return false;
        }


        if (typeof data != 'undefined' && typeof index !== 'undefined') {
            this.results[index] = data;
        }

        this.running = false;
        this.finish && this.finish(true, this.results);
    }

    private next(index?: number, data?: any): boolean {
        if (!this.running) {
            return false;
        }

        if (typeof data !== 'undefined' && typeof index !== 'undefined') {
            this.results[index] = data;
        }

        if (++this.count === this.list.length) {
            this.end && this.end();
        }

        return true;
    }

    private retry(index: number): boolean {
        if (!this.running) {
            return false;
        }

        const taskItem = this.list[index];
        taskItem && taskItem.excute(
            (data?: any) => this.next(index, data),
            (timeout = 0) => timeout > 0 ? (setTimeout(() => this.retry(index), timeout * 1000), 'ASYNC') : this.retry(index),
            (data?: any) => this.end(index, data)
        );

        return true;
    }
}

@injectable('TaskManager')
export default class TaskManager extends InjectableObject {
    public createSync(): Sync {
        return new Sync();
    }

    public createASync(): ASync {
        return new ASync();
    }

    public excute(fun: (retry: (timeout?: number) => void) => void) {
        fun(function retry(timeout = 0) {
            if (timeout > 0) {
                setTimeout(() => this.excute(fun), timeout * 1000);
            } else {
                this.excute(fun);
            }
        });
    }
};